#include "debug.h"
#include "interrupt.h"
#include "list.h"
#include "sync.h"
#include "thread.h"

void sema_init(struct semaphore_t* sema, uint8_t value)
{
  sema->value = value;
  list_init(&sema->waiters);
}

void sema_down(struct semaphore_t* sema)
{
  // 关中断，保证sema_down操作的原子性
  enum intr_status old_status = intr_disable();
  // 此时已经没有资源，等待资源
  while (sema->value == 0)
  {
    struct list_elem* cur_PCB_ready_list_tag =
        &cur_thread_PCB_get()->ready_list_tag;
    // 此时当前进程不应该在等待队列中
    ASSERT(!elem_find(&sema->waiters, cur_PCB_ready_list_tag));

    if (elem_find(&sema->waiters, cur_PCB_ready_list_tag))
    {
      PANIC("sema_down: blocked thread has been in waiters list");
    }

    list_append(&sema->waiters, cur_PCB_ready_list_tag);
    // 执行到下面这句话之后，线程就中断运行了，直到调用的线程被unblock
    thread_block(TASK_BLOCKED);
  }
  sema->value--;
  ASSERT(sema->value == 0);
  intr_set_status(old_status);
}

void sema_up(struct semaphore_t* sema)
{
  // 保证对sema操作的原子性，需要关中断
  enum intr_status old_status = intr_disable();
  ASSERT(sema->value == 0);
  if (!list_empty(&sema->waiters))
  {
    struct task_struct* blocked_thread = elem2entry(
        struct task_struct, ready_list_tag, list_pop(&sema->waiters));
    // 此时中断还是关着的，所以unblock讲被阻塞的线程放到ready_list中也没关系，因为时钟中断不会被响应
    // 所以一定是可以继续执行到下面的sema->value++的
    thread_unblock(blocked_thread);
  }
  sema->value++;
  ASSERT(sema->value == 1);
  intr_set_status(old_status);
}

void mutex_init(struct mutex_t* mutex)
{
  mutex->holder = NULL;
  mutex->holder_repeat_nr = 0;
  sema_init(&mutex->semaphore, 1);
}

void mutex_acquire(struct mutex_t* mutex)
{
  if (mutex->holder != cur_thread_PCB_get())
  {
    sema_down(&mutex->semaphore);
    mutex->holder = cur_thread_PCB_get();
    // 能从sema_down中出来，就一定有资源
    ASSERT(mutex->holder_repeat_nr == 0);
    mutex->holder_repeat_nr = 1;
  }
  else
  {
    mutex->holder_repeat_nr++;
  }
}

void mutex_release(struct mutex_t* mutex)
{
  // 只有当前正在运行的进程才能释放其所占有的锁
  ASSERT(mutex->holder == cur_thread_PCB_get());
  if (mutex->holder_repeat_nr > 1)
  {
    mutex->holder_repeat_nr--;
    return;
  }
  // 能运行到这里，一定是等于1
  ASSERT(mutex->holder_repeat_nr == 1);
  mutex->holder = NULL;
  mutex->holder_repeat_nr = 0;
  sema_up(&mutex->semaphore);
}
